package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.gateway.properties.UrlPathProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;

@Component
@Slf4j
public class UserAuthFilter implements GlobalFilter , Ordered {

    @Autowired
    private UrlPathProperties urlPathProperties ;

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    // 创建一个路径匹配器对象
    private static final AntPathMatcher antPathMatcher = new AntPathMatcher() ;

    /**
     * 拦截请求
     * @param exchange 封装了请求和响应对象
     * @param chain 实现放行操作
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        /**
         * 不论访问后端系统的哪一个资源都需要经过filter方法，但是并不是所有的资源都需要用户登录。
         * 对资源进行分类：
         * 1、静态资源: 图片 、 css、 js..  不需要验证用户的登录状态
         * 2、受保护的资源: 要求用户必须登录才可以访问的资源
         * 3、其他资源：用户登录和不登录都可以访问的资源
         */
        // 获取真实的请求的url
        ServerHttpRequest httpRequest = exchange.getRequest();
        String path = httpRequest.getURI().getPath();

        // 处理静态资源
        for(String noAuthUrl : urlPathProperties.getNoauthurls()) {
            boolean match = antPathMatcher.match(noAuthUrl, path);
            if(match) {
                log.info("path: {}" , path);
                return chain.filter(exchange);
            }
        }

        // 处理受保护的资源
        List<String> authurls = urlPathProperties.getAuthurls();
        for(String authurl : authurls) {
            boolean match = antPathMatcher.match(authurl, path);
            if(match) {     // 说明就是受保护的资源，校验登录状态
                // 获取token
                String token = getToken(exchange) ;
                if(StringUtils.isEmpty(token)) {   // 没有携带token
                    // 把用户踢回到登录页面
                    return toLoginPage(exchange , chain) ;
                }else {     // 携带了token，校验token合法性

                    // 根据token获取用户数据
                    UserInfo userInfo = getUserInfo(token) ;
                    if(userInfo == null) {
                        // 把用户踢回到登录页面
                        return toLoginPage(exchange , chain) ;
                    }else {
                        return userIdThrought(exchange , chain , userInfo);          // 用户id的透传
                    }
                }
            }
        }

        // 其他资源：用户登录和不登录都可以访问的资源
        String token = getToken(exchange);  // fb6ee49a86
        if(StringUtils.isEmpty(token)) {        // 没有登录直接进行放行
            return userTempIdThrought(exchange, chain) ;
        }else {         // 如果登录了，在放行之前需要透传用户的id

            // 获取用户数据
            UserInfo userInfo = getUserInfo(token);
            if(userInfo == null) {   // 伪造令牌
                return toLoginPage(exchange , chain) ;
            }else {
                return userIdThrought(exchange , chain , userInfo);          // 用户id的透传
            }
        }

    }

    // 在放行之前透传临时用户的id
    private Mono<Void> userTempIdThrought(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取临时用户的id
        String userTempId = getUserTempId(exchange);

        // 透传临时用户的id
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header("userTempId", userTempId).build();
        ServerWebExchange serverWebExchange = exchange.mutate().request(serverHttpRequest).response(exchange.getResponse()).build();
        return chain.filter(serverWebExchange) ;
    }

    public String getUserTempId(ServerWebExchange exchange) {

        // 获取request对象
        ServerHttpRequest httpRequest = exchange.getRequest();
        HttpCookie httpCookie = httpRequest.getCookies().getFirst("userTempId");
        if(httpCookie != null) {
            String value = httpCookie.getValue();
            return value ;
        }else {
            String token = httpRequest.getHeaders().getFirst("userTempId");
            return token ;
        }

    }

    // 进行用户id的透传
    private Mono<Void> userIdThrought(ServerWebExchange exchange, GatewayFilterChain chain, UserInfo userInfo) {

        // 获取临时用户的id
        String userTempId = getUserTempId(exchange);

        // 构建一个新的Request对象 , 把用户id存储到新的request对象的请求头中
        ServerHttpRequest httpRequest = exchange.getRequest();
        ServerHttpRequest serverHttpRequest = httpRequest.mutate()
                .header("userId" , String.valueOf(userInfo.getId()))
                .header("userTempId" , userTempId)
                .build();

        // 在放行的时候使用新的request对象，后端业务微服务获取userId的时候就直接从request对象的请求头中进行获取
        ServerWebExchange webExchange = exchange.mutate().request(serverHttpRequest).response(exchange.getResponse()).build();
        return chain.filter(webExchange) ;

    }

    // 把用户踢回到登录页面的方法
    private Mono<Void> toLoginPage(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取目标资源的请求路径
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().toString();

        // 获取响应对象
        ServerHttpResponse httpResponse = exchange.getResponse();

        // 设置302的状态码
        httpResponse.setStatusCode(HttpStatus.FOUND) ;

        // 设置location的响应头
        httpResponse.getHeaders().set("location" , urlPathProperties.getLoginpage() +"?originUrl=" + path);

        // 如果是伪造令牌把cookie中token删除掉
        ResponseCookie responseCookie = ResponseCookie.from("token", "").maxAge(-1).path("/").domain(".gmall.com").build();
        httpResponse.addCookie(responseCookie);

        // 结束请求
        return httpResponse.setComplete() ;

    }

    // 根据token从Redis中获取用户数据
    private UserInfo getUserInfo(String token) {
        String userInfoJSON = redisTemplate.opsForValue().get("user:login:" + token);
        if(StringUtils.isEmpty(userInfoJSON)) {
            return null ;
        }else {
            UserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);
            return userInfo ;
        }
    }

    // 获取token
    /**
     * 前端传递token的方式
     * 1、通过cookie进行传递
     * 2、通过请求头传递
     */
    private String getToken(ServerWebExchange exchange) {

        // 获取request对象
        ServerHttpRequest httpRequest = exchange.getRequest();
        HttpCookie httpCookie = httpRequest.getCookies().getFirst("token");
        if(httpCookie != null) {
            String value = httpCookie.getValue();
            return value ;
        }else {
            String token = httpRequest.getHeaders().getFirst("token");
            return token ;
        }

    }

    @Override
    public int getOrder() {         // 返回当前这个全局过滤器的排序值，框架会调用该方法获取该值和框架内部的过滤器进行排序
        return -1 ;
    }
}
